package com.ionehe.group.common.wechat.component;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Throwables;
import com.google.gson.Gson;
import com.ionehe.group.common.wechat.config.WechatConfig;
import com.ionehe.group.common.wechat.exception.WechatException;
import com.ionehe.group.common.wechat.model.AccessToken;
import com.ionehe.group.common.wechat.model.Code2Session;
import com.ionehe.group.common.wechat.model.PhoneInfoResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Map;

/**
 * 微信接口
 *
 * @author 2022/1/7 <a href="mailto:a@ionehe.com">xiu</a>
 */
@Component
@Slf4j
public class WechatAPI {
    @Resource
    @Lazy
    private WechatRequestAccessTokenCache requestAccessTokenCache;

    /**
     * 获取微信访问令牌
     *
     * @return 访问令牌
     */
    public AccessToken getRequestAcct(WechatConfig config) {
        log.info("WechatAPI[]getRequestAcct[]get access token by appid and secret[]config:{}", config);

        StringBuilder sb = new StringBuilder();
        sb.append(config.getAccessTokenUrl()).append("?appid=").append(config.getAppID()).append("&secret=")
                .append(config.getAppSecret()).append("&grant_type=").append(WechatConstant.AT_GRANT_TYPE);
        String url = sb.toString();

        log.info("WechatAPI[]getRequestAcct[]get access token by appid and secret[]url:{}", url);
        String res = HttpUtil.get(url);

        if (!success(res)) {
            log.error("Get the wechat access token error! cause:{}", res);
            throw new WechatException("request result error");
        }

        log.info("Get the wechat request access token successfully! request access token:{}", res);

        return new Gson().fromJson(res, AccessToken.class);
    }

    /**
     * 获取微信公众号公众号openid
     *
     * @param code   授权码
     * @param config 配置
     * @return {@link AccessToken}
     */
    public AccessToken getOpenIdPublicAcct(String code, WechatConfig config) {
        log.info("WechatAPI[]getOpenIdPublicAcct[]get openid by code[]code:{}[]config:{}", code, config);

        StringBuilder sb = new StringBuilder();
        sb.append(config.getOpenidUrl()).append("?appid=").append(config.getAppID()).append("&secret=").append(config.getAppSecret()).
                append("&code=").append(code).append("&grant_type=").append(WechatConstant.GRANT_TYPE);
        String url = sb.toString();

        log.info("WechatAPI[]getOpenIdPublicAcct[]get openid by code[]url:{}", url);
        String res = HttpUtil.get(url);

        log.info("WechatAPI[]getOpenIdPublicAcct[] result:{}", res);
        return new Gson().fromJson(res, AccessToken.class);
    }

    /**
     * 获取微信小程序openid
     *
     * @param code   授权码
     * @param config 配置
     * @return {@link Code2Session}
     */
    public Code2Session getOpenIdMiniPro(String code, WechatConfig config) {
        log.info("WechatAPI[]getOpenIdMiniPro[]get openid by code[]code:{}[]config:{}", code, config);

        StringBuilder sb = new StringBuilder();
        sb.append(config.getOpenidUrl()).append("?appid=").append(config.getAppID()).append("&secret=").append(config.getAppSecret()).
                append("&js_code=").append(code).append("&grant_type=").append(WechatConstant.GRANT_TYPE);
        String url = sb.toString();

        log.info("WechatAPI[]getOpenIdMiniPro[]get openid by code[]url:{}", url);
        String res = HttpUtil.get(url);

        log.info("WechatAPI[]getOpenIdMiniPro[]result:{}", res);

        res = res.replace("\\", "");

        return new Gson().fromJson(res, Code2Session.class);
    }

    /**
     * 获取用户手机号信息
     *
     * @param code   授权码
     * @param config 配置
     * @return 手机号信息
     */
    public PhoneInfoResponse.PhoneInfo getUserPhoneNumber(String code, WechatConfig config) {
        log.info("WechatAPI[]getUserPhoneNumber[]get the wechat user phone number by code, code:{}", code);

        StringBuilder sb = new StringBuilder(config.getGetUserPhoneUrl());
        sb.append("?access_token=").append(requestAccessTokenCache.findAccessToken(config));
        String url = sb.toString();

        JSONObject body = new JSONObject();
        body.put("code", code);

        log.info("WechatAPI[]getUserPhoneNumber[]get the wechat user phone number by code and request access token, url:{}", url);
        String res = HttpUtil.post(url, body.toString());

        PhoneInfoResponse response = new Gson().fromJson(res, PhoneInfoResponse.class);

        if (!response.success()) {
            log.error("Get the wechat user phone number error! cause:{}", res);
            throw new WechatException("request result error");
        }

        log.info("Get the wechat user phone number successfully! user phone info:{}", response.getPhoneInfo());
        return response.getPhoneInfo();
    }

    /**
     * 获取小程序码，适用于需要的码数量极多的业务场景。通过该接口生成的小程序码，永久有效，数量暂无限制。
     *
     * @param page  页面 page，例如 pages/index/index，根路径前不要填加 /，不能携带参数（参数请放在scene字段里），如果不填写这个字段，默认跳主页面
     * @param width 二维码的宽度，单位 px，最小 280px，最大 1280px，默认280px
     * @param param 最大32个可见字符，只支持数字，大小写英文以及部分特殊字符：!#$&'()*+,/:;=?@-._~，其它字符请自行编码为合法字符（因不支持%，中文无法使用 urlencode 处理，请使用其他编码方式）
     * @return 二维码字节流
     */
    public Byte[] getQrCodeUnLimit(String page, Integer width, Map<String, String> param, WechatConfig config) {
        if (StrUtil.isBlank(page)) {
            log.warn("WechatService[]getQrCodeUnLimit[]get un limit qrcode failed, page:{}", page);
            throw new WechatException("page null error");
        }

        StringBuilder sb = new StringBuilder(config.getGetWxCodeUnLimitUrl());
        sb.append("?access_token=").append(requestAccessTokenCache.findAccessToken(config));
        String url = sb.toString();

        JSONObject body = new JSONObject();
        body.put("page", page);
        body.put("width", width == null || width <= 280 ? 280 : width);
        if (CollUtil.isNotEmpty(param)) {
            body.put("scene", getQueryStr(param));
        }

        log.info("Send the request to wechat, get the un limit qr code! url:{}, body:{}", url, body);
        String res = HttpUtil.post(url, body.toString());

        if (!success(res)) {
            log.error("Get the wechat un limit qr code error! cause:{}", res);
            throw new WechatException("request result error");
        }

        Byte[] bytes = null;
        try {
            bytes = JSONUtil.toBean(res, Byte[].class);
        } catch (Exception e) {
            log.error("WechatService[]getQrCodeUnLimit[]error! result:{}, cause:{}", res, Throwables.getStackTraceAsString(e));
        }

        log.info("Get the wechat generate QR code successfully!");

        return bytes;
    }

    /**
     * 请求是否成功
     *
     * @param wechatRes 微信返回报文
     * @return 成功或失败
     */
    private boolean success(String wechatRes) {
        return !wechatRes.contains("errcode");
    }

    /**
     * 得到query的字符串
     *
     * @param queries 查询
     * @return query类型的字符串
     */
    private String getQueryStr(Map<String, String> queries) {
        StringBuilder buff = new StringBuilder();
        String[] sortedKeys = queries.keySet().toArray(new String[0]);
        Arrays.sort(sortedKeys);
        String[] var3 = sortedKeys;
        int var4 = sortedKeys.length;

        for (int var5 = 0; var5 < var4; ++var5) {
            String sortedKey = var3[var5];
            if (var5 > 0) {
                buff.append("&");
            }
            buff.append(sortedKey).append("=").append(queries.get(sortedKey));
        }

        return buff.toString();
    }
}
